package com.simple.crawler.core.resolver.ext;

import com.simple.crawler.annotation.Setter;
import com.simple.crawler.constant.Const;
import com.simple.crawler.core.CrawlerContext;
import com.simple.crawler.core.resolver.Resolver;
import com.simple.crawler.utils.ReflectUtils;
import com.simple.crawler.utils.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class JQuery implements Resolver {

    public Document parse(String html) {
        if (html == null || StringUtils.isBlank(html)) {
            return null;
        }
        return Jsoup.parse(html);
    }

    public String title(Document document) {
        return document.title();
    }

    public String html(Document document, String selector) {
        return document.selectFirst(selector).html();
    }

    public String text(Document document, String selector) {
        return document.selectFirst(selector).text();
    }

    public Element selectOne(Document document, String selector) {
        return document.selectFirst(selector);
    }

    public Elements selectAll(Document document, String selector) {
        return document.select(selector);
    }

    public Element selectOne(Element element, String selector) {
        return element.selectFirst(selector);
    }

    public Elements selectAll(Element element, String selector) {
        return element.select(selector);
    }

    public <T> T selectObj(Document document, String selector, Class<T> type) {
        Element element = selectOne(document, selector);
        if (element == null) {
            return null;
        }
        return doSetter(type, element);
    }

    public <T> List<T> selectObjList(Document document, String selector, Class<T> type) {
        Elements elements = selectAll(document, selector);
        if (elements == null || elements.isEmpty()) {
            return new ArrayList<>();
        }
        return elements.stream().map(element -> doSetter(type, element)).collect(Collectors.toList());
    }

    private void doSetter(Object obj, Field field, Element parent) {
        Setter setter = field.getDeclaredAnnotation(Setter.class);
        String expr = StringUtils.getMatchObj(setter.expr(), Const.DEFAULT_EXPR_PATTERN);
        Elements target = selectAll(parent, expr);
        if (target == null) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        if (setter.expr().matches(Const.EXPR_NODE_PATTERN)) {
            sb.append(target.html());
        } else if (setter.expr().matches(Const.EXPR_TEXT_PATTERN)) {
            sb.append(target.text());
        } else if (setter.expr().matches(Const.EXPR_ATTR_PATTERN)) {
            sb.append(target.attr(StringUtils.getMatchObj(setter.expr(), Const.EXPR_ATTR_PATTERN, 2)));
        }
        if (String.class.equals(field.getType())) {
            ReflectUtils.setFieldValue(obj, field.getName(), setter.prefix() + sb.toString() + setter.suffix());
        } else if (Number.class.equals(field.getType().getSuperclass())) {
            ReflectUtils.setFieldValue(obj, field.getName(), StringUtils.convertToNumber(setter.prefix() + sb.toString() + setter.suffix(), field.getType()));
        } else if (List.class.equals(field.getType())) {
            ReflectUtils.setFieldValue(obj, field.getName(), target.stream().map(e -> doSetter(ReflectUtils.getListActualType(field), e)).collect(Collectors.toList()));
        } else {
            ReflectUtils.setFieldValue(obj, field.getName(), doSetter(field.getType(), target.first()));
        }
        if (StringUtils.isNotBlank(setter.cacheKey())) {
            CrawlerContext.addToCache(setter.cacheKey(), ReflectUtils.getFieldValue(obj, field));
        }
    }

    private  <T> T doSetter(Class<T> type, Element parent) {
        T obj = ReflectUtils.newInstance(type);
        List<Field> fieldList = ReflectUtils.getFields(type, Setter.class, new ArrayList<>());
        if (fieldList == null || fieldList.isEmpty()) {
            return obj;
        }
        fieldList.forEach(field -> doSetter(obj, field, parent));
        return obj;
    }

    @Override
    public void doSetter(Object obj, Field field, String html) {
        if (StringUtils.isBlank(html)) {
            return;
        }
        doSetter(obj, field, parse(html));
    }
}
